﻿#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// ScrumTable for Scrum Meeting Support
// Copyright © HSR - Hochschule für Technik Rapperswil
// All Rights Reserved
// 
// Author:
//  Michael Gfeller Silvan Gehrig Patrick Boos
// 
// ---------------------------------------------------------------------
#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Threading;
using Microsoft.Practices.Unity;
using ScrumTable.Common;
using ScrumTable.Common.Logging;
using ScrumTable.Config.ConfigManagement;
using ScrumTable.BL.DM.DataManagement;
using ScrumTable.Config.UserManagement;
using ScrumTable.DL.Data;
using ScrumTable.DL.Data.Common;
using ScrumTable.Common.Monitoring;
using ScrumTable.Common.Threading;
using ScrumTable.DL.Data.Common.Config.Driver;
using ScrumTable.DL.Data.Common.Config.Schema;
using ScrumTable.UI.ViewModel.Controller;
using ScrumTable.UI.ViewModel.Extension;
using ScrumTable.Common.Collections;
#endregion

namespace ScrumTable.UI.ViewModel.Data
{
    /// <summary>
    /// Description of the class
    /// </summary>
    public class ScrumViewModelData : ViewModelBase
    {

        #region Declarations

        //--------------------------------------------------------------------
        // Declarations
        //--------------------------------------------------------------------
        private bool _isDataLoaded = false;
        private Project _currentProject;
        private ObservableCollection<MemberViewModel> _currentMembers;
        private Iteration _iteration;
        private IDomainContext _dataContex;
        private string _filter;
        private string _filterIntern;
        private readonly DispatcherTimer _timer = new DispatcherTimer {Interval = TimeSpan.FromSeconds(0.5)};
        private MemberViewModel _currentMember;

        #endregion

        #region Properties

        //--------------------------------------------------------------------
        // Properties
        //--------------------------------------------------------------------

        /// <summary>
        /// Gets or sets the container.
        /// </summary>
        /// <value>The container.</value>
        [Dependency]
        public IUnityContainer Container { get; set; }

        /// <summary>
        /// Gets or sets the configmanager.
        /// </summary>
        /// <value>The configmanager.</value>
        [Dependency]
        public ConfigManager ConfigManager { get; set; }

        /// <summary>
        /// Gets or sets the data context.
        /// </summary>
        /// <value>The data context.</value>
        public IDomainContext DataContext
        {
            get { return _dataContex; }
            set
            {
                if (_dataContex != null)
                {
                    _isDataLoaded = false;
                    _dataContex.Dispose();
                }
                _dataContex = value;
                SetCurrentProjectAndIteration(AccountManager.CurrentLoginAccount.ProjectId, AccountManager.CurrentLoginAccount.IterationId);
                SendPropertyChanged("DataContext");
            }
        }



        /// <summary>
        /// Gets or sets the account manager.
        /// </summary>
        /// <value>The account manager.</value>
        [Dependency]
        public LoginAccountManager AccountManager { get; set; }

        /// <summary>
        /// Gets if multiple user can log in
        /// </summary>
        public bool IsMultipleLogin
        {
            get { return CurrentProject != null && (from x in CurrentProject.Members where x.CanLogIn select x).Count() > 1; }
        }


        /// <summary>
        /// The current filter, placed on all items. Least string length is 3
        /// </summary>
        public string Filter
        {
            get { return _filter; }

            set
            {
                if (value == _filter)
                {
                    return;
                }

                _timer.Stop();
                _filter = value;
                _timer.Start();
            }
        }



        public ObservableCollection<MemberViewModel> CurrentMembers
        {
            get
            {
                if (CurrentProject == null)
                {
                    return null;
                }
                var members = from x in CurrentProject.Members
                              where !AccountManager.ContainsHiddenMember(x.Name)
                              select new MemberViewModel(x);


                if (_currentMembers == null)
                {
                    _currentMembers = new ObservableCollection<MemberViewModel>(members);
                    _currentMembers.UnionToLeft<MemberViewModel>(new List<MemberViewModel>(members));
                }
                else
                {
                    _currentMembers.UnionToLeft<MemberViewModel>(new List<MemberViewModel>(members));
                }

                _currentMembers.ForEach(x =>
                                            {
                                                x.WorkForIteration = CurrentIteration;
                                            });

                return _currentMembers;
            }
        }

        /// <summary>
        /// the current member which is loged in
        /// </summary>
        public MemberViewModel CurrentMember
        {
            get
            {
                if (_currentMember == null && CurrentProject != null && CurrentProject.Members.Count > 0)
                {
                    _currentMember = new MemberViewModel(CurrentProject.Members.Where(x => x.CanLogIn).FirstOrDefault());
                }
                return _currentMember;
            }
            set
            {
                if (!value.CanLogIn || _currentMember == value)
                {
                    SendPropertyChanged("CurrentMember");
                    return;
                }

                Container.Resolve<IRunTaskWithGuiSupplie>().ChangeUser((e) =>
                                                                           {
                                                                               if (e == SaveState.Completed ||
                                                                                   e == SaveState.Quit)
                                                                               {
                                                                                   _currentMember = value;
                                                                               }
                                                                               SendPropertyChanged("CurrentMember");

                                                                           }, value.Original);

            }
        }


        /// <summary>
        /// Gets or sets the current iteration.
        /// </summary>
        /// <value>The current iteration.</value>
        public Iteration CurrentIteration
        {
            get { return _iteration; }
            set
            {
                _iteration = value;
                if (value != null)
                {

                    CurrentMembers.ForEach(x =>
                                               {
                                                   x.WorkForIteration = value;
                                               });
                    AccountManager.CurrentLoginAccount.IterationId = value.Name;
                }
                SendPropertyChanged("CurrentIteration");
            }
        }

        /// <summary>
        /// Gets or sets the current project.
        /// </summary>
        /// <value>The current project.</value>
        public Project CurrentProject
        {
            get { return _currentProject; }
            set
            {
                _currentProject = value;
                SendPropertyChanged("CurrentProject");
                SendPropertyChanged("CurrentMembers");
                if (value != null)
                {
                    ElementManager.SetCurrentConfig(value);

                    AccountManager.CurrentLoginAccount.ProjectId = value.Name;
                    if (_currentProject.Iterations.Count > 0)
                        CurrentIteration = _currentProject.Iterations.First();
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the current user is connected.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the user is connected; otherwise, <c>false</c>.
        /// </value>
        public bool IsUserConnected
        {
            get { return (_dataContex == null) ? false : _dataContex.IsConnected && _isDataLoaded; }
            set
            {

                SendPropertyChanged("IsUserConnected");
                SendPropertyChanged("IsDataContextConnected");
                if (value)
                {
                    Container.Resolve<ScrumController>().Login();
                }
                else
                {
                    Container.Resolve<ScrumController>().LogOut();
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the datacontext is connected.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the datacontext is connected; otherwise, <c>false</c>.
        /// </value>
        public bool IsDataContextConnected
        {
            get { return (_dataContex == null) ? false : _dataContex.IsConnected; }
        }

        #endregion


        #region Constructors / Destructor

        //--------------------------------------------------------------------
        // Constructors / Destructor
        //--------------------------------------------------------------------

        /// <summary>
        /// Initializes a new instance of the <see cref="ScrumViewModelData"/> class.
        /// </summary>
        public ScrumViewModelData()
        {
            _timer.Tick += (sender, args) =>
                               {
                                   _timer.Stop();
                                   _filterIntern = (string.IsNullOrEmpty(_filter)) ? String.Empty : _filter.ToLower();
                                   SendPropertyChanged("Filter");
                               };
        }

        #endregion


        #region Methods

        //--------------------------------------------------------------------
        // Methods
        //--------------------------------------------------------------------


        public static IDataSchemaSource GetSchema(string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                return null;
            }
            var assembly = GetDataAssembly(((IAppScreen) Application.Current.MainWindow).Container.Resolve<LoginAccountManager>().CurrentLoginAccount.DataType);
            if (assembly != null)
            {
                return (from dataSchema in assembly.DataSchemaSelector
                        where dataSchema.Name == name || dataSchema.LegacyNames.Contains(name)
                        select dataSchema).FirstOrDefault();
            }
            return null;
        }
        
        public static DataAssembly GetDataAssembly(string description)
        {

            foreach (var x in DataConnectionFactory.CachedDataAssemblies)
            {
                if (x.Description == description)
                {
                    return x;
                }
            }
            return null;
        }


        /// <summary>
        /// Adds a member to the hidden list.
        /// </summary>
        /// <param name="name">The name.</param>
        public void HideMember(string name)
        {
            AccountManager.AddHiddenMember(name);
            SendPropertyChanged("CurrentMembers");
        }

        /// <summary>
        /// Removes a member from the hidden list.
        /// </summary>
        /// <param name="name">The name.</param>
        public void ShowMember(string name)
        {
            AccountManager.RemoveHiddenMember(name);
            SendPropertyChanged("CurrentMembers");
        }

        /// <summary>
        /// Sets the current project and iteration.
        /// </summary>
        /// <param name="project">The project.</param>
        /// <param name="iteration">The iteration.</param>
        public void SetCurrentProjectAndIteration(string project, string iteration)
        {
            if (SetProject(project))
            {
                SetIteration(iteration);
            }
        }


        /// <summary>
        /// SLOW (very slow)  Search item per id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public IScrumItem FindScrumItem(string id)
        {
            return FindUserStory(id) ?? (IScrumItem) FindTask(id) ?? FindBug(id);
        }


        /// <summary>
        /// SLOW (very slow)  Search item per id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public IScrumItem FindScrumItem<T>(string id)
            where T : ScrumItemBase, IScrumItem
        {
            if (typeof (T) == typeof (Task))
            {
                return FindTask(id);
            }
            if (typeof (T) == typeof (UserStory))
            {
                return FindUserStory(id);
            }
            if (typeof (T) == typeof (Bug))
            {
                return FindBug(id);
            }
            return FindScrumItem(id);
        }



        /// <summary>
        /// SLOW (very slow)  Search item per id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public UserStory FindUserStory(string id)
        {
            var foundstory = CurrentProject.Stories.FirstOrDefault(x => x.Id == id) ??
                             (from it in CurrentProject.Iterations from story in it.Stories where story.Id == id select story).FirstOrDefault();
            return foundstory;
        }

        /// <summary>
        /// SLOW (very slow)  Search item per id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public Task FindTask(string id)
        {
            var foundTask = (from story in CurrentProject.Stories from task in story.Tasks where task.Id == id select task).FirstOrDefault();
            if (foundTask == null)
            {
                foundTask = (from it in CurrentProject.Iterations from story in it.Stories from task in story.Tasks where task.Id == id select task).FirstOrDefault();
            }
            return foundTask;
        }

        /// <summary>
        /// SLOW (very slow)  Search item per id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public Bug FindBug(string id)
        {
            var foundBug = CurrentProject.Bugs.FirstOrDefault(x => x.Id == id) ?? (from it in CurrentProject.Iterations from bug in it.Bugs where bug.Id == id select bug).FirstOrDefault();
            if (foundBug == null)
            {
                foundBug = (from story in CurrentProject.Stories from bug in story.Bugs where bug.Id == id select bug).FirstOrDefault();
                if (foundBug == null)
                {
                    foundBug = (from it in CurrentProject.Iterations from story in it.Stories from bug in story.Bugs where bug.Id == id select bug).FirstOrDefault();
                }
            }
            return foundBug;
        }


        /// <summary>
        /// Connects the user to the data source. 
        /// </summary>
        public void Connect(string pin, NavigationTarget target, Action<String> errorCallback, Action connectOkCallback = null)
        {
            Connect(pin, target, false, errorCallback);
        }

        /// <summary>
        /// Connects the user to the data source.
        /// </summary>
        public void Connect(string pin, NavigationTarget target, bool loadData, Action<string> errorCallback, Action connectOkCallback = null)
        {
            _iteration = null;
            _currentProject = null;
            _currentMembers = null;
            Container.Resolve<IRunTaskWithGuiSupplie>().RunTaskLoadingBar((dispose, dataimport) => TryConnectToDataContext(pin, target, dispose, dataimport, loadData, errorCallback));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pin"></param>
        /// <param name="target"></param>
        /// <param name="statusMonitorToDispose"></param>
        /// <param name="importMonitor"></param>
        /// <param name="loadData">normal case: dispose UI-Locks</param>
        /// <param name="errorCallback"></param>
        private void TryConnectToDataContext(string pin, NavigationTarget target, IDisposable statusMonitorToDispose, IDataImportMonitor importMonitor, bool loadData, Action<String> errorCallback)
        {
            var dispatcher = Container.Resolve<IDataSyncDispatcher>();
            var configuredDataContext = RetrieveDataContext();
            
            if (configuredDataContext == null)
            {
                ReportConnectionError(statusMonitorToDispose, errorCallback, new ScrumViewModelDataException("The specified project configuration references an unsupported data driver or schema."));
            }
            else
            {
                try
                {
                    ConnectToDataContext(pin, target, statusMonitorToDispose, importMonitor, loadData, errorCallback, configuredDataContext, dispatcher);
                }
                catch (LoginAccountConnectException)
                {
                    Container.Resolve<ScrumController>().GoTo(NavigationTarget.RegisterView);
                    statusMonitorToDispose.Dispose();
                }
            }
        }

        private void ConnectToDataContext(string pin, NavigationTarget target, IDisposable statusMonitorToDispose,
                                   IDataImportMonitor importMonitor, bool loadData, Action<string> errorCallback,
                                   IDomainContext configuredDataContext, IDataSyncDispatcher dispatcher)
        {
            AccountManager.Connect(
                dispatcher,
                pin,
                configuredDataContext,
                (isConnected, errorText) =>
                    {
                        if (isConnected)
                        {
                            if (!loadData)
                            {
                                NaviController.FireDataLogOut();
                                statusMonitorToDispose.Dispose();
                                DataContext = configuredDataContext;
                            }
                            else
                            {
                                var project = GetProjectFromName(
                                    configuredDataContext,
                                    AccountManager.CurrentLoginAccount.ProjectId);

                                if (project == null)
                                {
                                    ReportConnectionError(
                                        statusMonitorToDispose,
                                        errorCallback,
                                        new ScrumViewModelDataException("Invalid project configuration. Please correct your settings."));
                                }
                                else
                                {
                                    project.LoadData(
                                        new CallbackDataImportMonitor(
                                            result =>
                                                {
                                                    if (result == null || !(result is DataResult) || !((DataResult) result).HasError)
                                                    {
                                                        DataContext = configuredDataContext;
                                                        statusMonitorToDispose.Dispose();
                                                        _isDataLoaded = true;
                                                        IsUserConnected = true;
                                                        Container.Resolve<ScrumController>().GoTo(target);
                                                    }
                                                    else
                                                    {
                                                        ReportConnectionError(
                                                            statusMonitorToDispose,
                                                            errorCallback,
                                                            new ScrumViewModelDataException(
                                                                "Connection aborted: " +
                                                                ((DataResult) result).Error.ToMessageErrorString(),
                                                                ((DataResult) result).Error));
                                                    }
                                                }, importMonitor));
                                }
                            }
                        }
                        else
                        {
                            ReportConnectionError(statusMonitorToDispose, errorCallback,
                                                  new ScrumViewModelDataException("Connection Failed: " + errorText));
                        }
                        SendPropertyChanged("IsUserConnected");
                        SendPropertyChanged("IsDataContextConnected");
                    });
        }

        private void ReportConnectionError(IDisposable statusMonitorToDispose, Action<string> errorCallback, Exception error)
        {
            if (errorCallback != null)
            {
                Logger<ScrumViewModelData>.Error(error);
                errorCallback(error.Message);
            }
            statusMonitorToDispose.Dispose();
        }

        private IDomainContext RetrieveDataContext()
        {
            var assembly = (from x in DataConnectionFactory.CachedDataAssemblies
                             where x.Description == AccountManager.CurrentLoginAccount.DataType
                             select x).FirstOrDefault();

#if !RELEASE
            if (assembly == null)
            {
                // needed for auto-login
                assembly = (from x in DataConnectionFactory.CachedDataAssemblies
                             where x.Description == "ScrumTable - Local XML data storage assembly."
                             select x).FirstOrDefault();
                
            }
#endif
   
            var assemblySchema = GetSchema(AccountManager.CurrentLoginAccount.SchemaName);

            if (assemblySchema != null && assembly != null)
            {
                string configName = AccountManager.CurrentLoginAccount.GetConfigForConnection(AccountManager.CurrentLoginAccount.DataType);

                if (!string.IsNullOrEmpty(configName))
                {
                    assembly.DriverConfigSelector.Selected =
                        assembly.DriverConfigSelector.FirstOrDefault(x => x.Name == configName) ??
                        assembly.DriverConfigSelector.Default;
                }
                else
                {
                    assembly.DriverConfigSelector.Selected = assembly.DriverConfigSelector.Default;
                }
                assembly.DataSchemaSelector.Selected = assemblySchema;

                return DomainContextFactory.Create(DataConnectionFactory.Create(assembly));
            }
            return null;
        }


        /// <summary>
        /// Disconnects the user from the data source.
        /// </summary>
        public void Disconnect(bool logOut = true)
        {
            if (logOut)
            {
                IsUserConnected = false;
            }
            if (_isDataLoaded)
            {
                Container.Resolve<IRunTaskWithGuiSupplie>().RunSave(
                    e =>
                        {
                            if (e == SaveState.Completed || e == SaveState.Quit)
                            {
                                ResetData();
                            }
                            else
                            {
                                IsUserConnected = true;
                            }
                        });
            }
            else
            {
                ResetData();
            }
        }

       

        private void ResetData()
        {
            _currentProject = null;
            _iteration = null;
            _currentMembers = null;
            _currentMember = null;
            if (DataContext != null)
            {
                DataContext.Disconnect();
            }
            ClearFilter();
            DataContext = null;
            SendPropertyChanged("IsUserConnected");
            SendPropertyChanged("IsDataContextConnected");
        }

        /// <summary>
        /// Sets the current project
        /// </summary>
        /// <param name="project">The project.</param>
        private bool SetProject(string project)
        {
            if (!string.IsNullOrEmpty(project) && DataContext!= null)
            {
                CurrentProject = GetProjectFromName(DataContext, project);
                return true;
               
            }
            return false;
        }

        /// <summary>
        /// Sets the current iteration
        /// </summary>
        /// <param name="iteration">The iteration.</param>
        private bool SetIteration(string iteration)
        {
            if (!string.IsNullOrEmpty(iteration))
            {
                if (CurrentProject != null && CurrentProject.Iterations.Count != 0)
                {
                    CurrentIteration = GetIterationFromName(CurrentProject, iteration); 
                    return true;
                }
            }
            return false;
        }

        private Project GetProjectFromName(IDomainContext context, string name)
        {
            if(context == null) { return null; }
            if(name == null) { return context.Projects.FirstOrDefault(); }
            var projects = from x in context.Projects where x.Name == name select x;
            if( projects.Count() ==0)
            {
                return context.Projects.FirstOrDefault();
            }
            return projects.FirstOrDefault();
        }

        private Iteration GetIterationFromName(Project project, string name)
        {
            if(project==null) { return null; }
            if(name == null) { return project.Iterations.First(); }

            var iterations = from x in project.Iterations where x.Name == name select x;
            if (iterations.Count() == 0)
            {
                return project.Iterations.First();
            }
            return iterations.First();
        }

        



        public bool ContainsFilter(object toFilter)
        {
            if (toFilter is UserStoryViewModel) { return ContainsFilter(((UserStoryViewModel)toFilter).Original); }
            if (toFilter is TaskViewModel) { return ContainsFilter(((TaskViewModel)toFilter).Original); }
            if (toFilter is BugViewModel) { return ContainsFilter(((BugViewModel)toFilter).Original); }
            if (toFilter is string) { return ContainsFilter((string)toFilter); }
            return true;
        }


        public bool ContainsFilter(Bug bug)
        {
            return string.IsNullOrEmpty(_filterIntern) || bug.Name.ToLower().Contains(_filterIntern);
        }

        public bool ContainsFilter(Task task)
        {
            return string.IsNullOrEmpty(_filterIntern) || task.Name.ToLower().Contains(_filterIntern);
        }

        public bool ContainsFilter(UserStory story)
        {
            bool ret = string.IsNullOrEmpty(_filterIntern) || story.Name.ToLower().Contains(_filterIntern);
            ret  = story.Tasks.Aggregate(ret, (current, task) => current || ContainsFilter(task));
            return ret;
        }


        public bool ContainsFilter(string text)
        {
            return string.IsNullOrEmpty(_filterIntern) || text.ToLower().Contains(_filterIntern);
        }

        public void ClearFilter()
        {
            _timer.Stop();
            _filter = null;
            _filterIntern = String.Empty;
            SendPropertyChanged("Filter");
        }

        #endregion

        #region Events       
        //--------------------------------------------------------------------
        // Events
        //--------------------------------------------------------------------
  
        #endregion

    }   
}